Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

input: reduce memory allocation for ScriptBuilder #7464

Merged
merged 5 commits into from
Apr 11, 2023

Conversation

guggero
Copy link
Collaborator

@guggero guggero commented Feb 27, 2023

Depends on btcsuite/btcd#1954.

I got this heap profile from a user and noticed the NewScriptBuilder showing up with massive allocations:

Screenshot from 2023-02-27 12-50-45

It turns out that function allocates 500 bytes of memory for each script, even if we only end up using about 20-140 bytes for most of our scripts.
I added a new functional option to specify the initial allocation and used that for all our scripts.

The difference is quite significant, it looks like we can save almost 50% of allocation space.

Before optimization:

BenchmarkScriptBuilderAlloc
BenchmarkScriptBuilderAlloc-24    	      14	 100126013 ns/op	14200361 B/op	  128001 allocs/op


After optimization:
BenchmarkScriptBuilderAlloc
BenchmarkScriptBuilderAlloc-24    	      13	  97141019 ns/op	 7208551 B/op	  162001 allocs/op

Copy link
Collaborator

@yyforyongyu yyforyongyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice!! Running benchmarks locally and confirming the results,

> benchstat old.prof new.prof
goos: darwin
goarch: arm64
pkg: github.com/lightningnetwork/lnd/input
                      │  old.prof   │              new.prof              │
                      │   sec/op    │   sec/op     vs base               │
ScriptBuilderAlloc-10   15.03m ± 4%   14.44m ± 0%  -3.93% (p=0.000 n=10)


                      │   old.prof    │               new.prof               │
                      │     B/op      │     B/op      vs base                │
ScriptBuilderAlloc-10   12.138Mi ± 0%   5.989Mi ± 0%  -50.66% (p=0.000 n=10)


                      │  old.prof   │               new.prof               │
                      │  allocs/op  │  allocs/op    vs base                │
ScriptBuilderAlloc-10   94.00k ± 0%   128.00k ± 0%  +36.17% (p=0.000 n=10)

This is LGTM once the mod is updated🎉

input/script_utils_test.go Show resolved Hide resolved

script, err = CommitScriptAnchor(randomPub)
require.NoError(t, err)
require.Len(t, script, 40)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got curious so I ran a version without the require.Len, and the results got slightly better with a 52.34% reduction in bytes per operation.

goos: darwin
goarch: arm64
pkg: github.com/lightningnetwork/lnd/input
                      │  old.prof   │              new.prof              │
                      │   sec/op    │   sec/op     vs base               │
ScriptBuilderAlloc-10   8.944m ± 2%   8.882m ± 2%  -0.69% (p=0.023 n=10)

                      │   old.prof    │               new.prof               │
                      │     B/op      │     B/op      vs base                │
ScriptBuilderAlloc-10   11.749Mi ± 0%   5.600Mi ± 0%  -52.34% (p=0.000 n=10)

                      │  old.prof   │               new.prof               │
                      │  allocs/op  │  allocs/op    vs base                │
ScriptBuilderAlloc-10   77.00k ± 0%   111.00k ± 0%  +44.16% (p=0.000 n=10)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you just run it like this?

	_, err = WitnessScriptHash(dummyData)
	require.NoError(t, err)

I think the compiler just optimizes away the return value, since it's not used. Could that explain the further 52% reduction?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think require.Len needs to put the value on the stack since it's referenced outside the function. Validated with a dummy test, which showed no alloc,

script, err = CommitScriptAnchor(randomPub)
require.NoError(t, err)
script[0] = 1 // make a dummy usage of the returned value

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I refactored the test a little bit to not use require inside the benchmark. Not sure if this is any better.

Copy link
Collaborator

@yyforyongyu yyforyongyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tACK🎉Thanks for the PR! (the linter needs to be fixed tho)


script, err = CommitScriptAnchor(randomPub)
require.NoError(t, err)
require.Len(t, script, 40)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think require.Len needs to put the value on the stack since it's referenced outside the function. Validated with a dummy test, which showed no alloc,

script, err = CommitScriptAnchor(randomPub)
require.NoError(t, err)
script[0] = 1 // make a dummy usage of the returned value

@guggero guggero added this to the v0.16.1 milestone Mar 13, 2023
@guggero guggero changed the base branch from master to 0-16-1-staging March 14, 2023 12:00
@guggero
Copy link
Collaborator Author

guggero commented Mar 14, 2023

Changed the base branch to 0-16-1-staging so this can be merged.

@guggero guggero requested a review from ffranr March 14, 2023 12:01
Copy link
Collaborator

@ffranr ffranr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look like worth while changes! 🥳

input/script_utils_test.go Show resolved Hide resolved
input/script_utils_test.go Outdated Show resolved Hide resolved
input/script_utils.go Outdated Show resolved Hide resolved
channeldb/graph.go Outdated Show resolved Hide resolved
@guggero guggero force-pushed the script-builder-capacity branch 3 times, most recently from 92a7d60 to b2ce7ff Compare March 29, 2023 12:01
@saubyk saubyk requested a review from ffranr March 30, 2023 15:20
@lightninglabs-deploy
Copy link

@ffranr: review reminder

@Roasbeef Roasbeef changed the base branch from 0-16-1-staging to master April 11, 2023 00:00
Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice optimization work! Only comment is if we can reuse some of the existing size estimate constants instead of plugging magic numbers into the prealloc arg.

LGTM 🍦

input/script_utils.go Outdated Show resolved Hide resolved
The default allocation of 500 bytes for the script that is
used in NewScriptBuilder is way too much for most of our scripts.
With the new functional option we can tune the allocation to exactly
what we need.
Copy link
Collaborator

@ffranr ffranr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work 👍

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎨

@Roasbeef Roasbeef merged commit 158e93f into lightningnetwork:master Apr 11, 2023
@guggero guggero deleted the script-builder-capacity branch April 12, 2023 07:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants